
#include "server.h"
#include "request.h"
#include "configuration.h"
#include "dispatcher.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/keyvalq_struct.h>
#include <event2/buffer.h>
#include <pthread.h>

Server *Server::_instance = NULL; // initialize singleton precall instance

void signal_handler(int sig) {
	pid_t pid; // no declarations within switch :)
	int status; // no declaraions within switch :)
	switch (sig) {
		case SIGTERM:
		case SIGHUP:
		case SIGINT:
			Server::instance().stop();
			break;
		case SIGCHLD:
			while ((pid = waitpid(-1, &status, WNOHANG)) > 0) ;
		default:
			; // nothing
	}
}

void *request_handler(void *req) {
	Request request((evhttp_request *)req);

	/*request.addHeader("Content-Type", "text/html;charset=utf-8");

	request.addResponse("<html>\n"
						"<head>\n<title>Hello</title>\n</head>\n"
						"<body>\n"
						"<h1>It works!</h1>\n");

	request.addResponse("<p>Path: %s</p>\n", request.path());
	request.addResponse("<p>GET query: %s, POST query: %s</p>\n", request.getQuery(), request.postQuery());

	request.addResponse("</body>\n</html>");

	request.sendResponse();*/
	Dispatcher dispatcher(&request);
	dispatcher.sendResponse();

	return NULL;
}

void request_cb(evhttp_request *req, void * /*ctx*/) {
	syslog(LOG_NOTICE, "Request from: %s:%d URI: %s", req->remote_host, req->remote_port, req->uri);

	pthread_t thread;
	if (pthread_create(&thread, NULL, request_handler, req)) {
		syslog(LOG_ERR, "Cannot start request handling thread");
		return;
	}
	pthread_detach(thread); // run, Forest, run!
}

Server &Server::instance() {
	if (!_instance) {
		_instance = new Server();
	}

	return *_instance;
}

int Server::start() {
	daemonize();
	setupSignals();
	setupEvents();

	_running = true;

	syslog(LOG_NOTICE, "started");

	spawnWorkers(Configuration::instance().workersNumber());

	event_base_dispatch(ebase);

	return 0;
}

void Server::stop() {
	if (_running) {
		//WorkerPool::instance().close();
		event_base_loopbreak(ebase);
		_running = false;
		if (ehttp) {
			evhttp_free(ehttp);
			ehttp = NULL;
		}
		if (ebase) {
			event_base_free(ebase);
			ebase = NULL;
		}
		if (is_master) {
			if (close(lockfile_fd)) {
				syslog(LOG_ERR, "Cannot close handle to lockfile %s, code %d (%s)",
						Configuration::instance().lockfile().c_str(), errno, strerror(errno));
			}
			if (unlink(Configuration::instance().lockfile().c_str())) {
				syslog(LOG_ERR, "Cannot unlink lockfile %s, code %d (%s)",
					Configuration::instance().lockfile().c_str(), errno, strerror(errno));
			}
			syslog(LOG_NOTICE, "master terminated");
		} else {
			syslog(LOG_NOTICE, "worker terminated");
		}
	}
}

Server::Server() :
	is_master(true), lockfile_fd(-1), _running(false), ebase(NULL), ehttp(NULL)
{
	//
}

Server::~Server() {
	closelog();
}

void Server::daemonize() {
	if (getppid() == 1) return; // init is our parent, we are already a daemon

	Configuration &config = Configuration::instance();

	if ((lockfile_fd = open(config.lockfile().c_str(), O_RDWR | O_CREAT | O_EXCL, 0640)) < 0) { // NOTICE: single 'equal' is correct here
		fprintf(stderr, "Unable to create lockfile %s, code %d (%s)\n", config.lockfile().c_str(), errno, strerror(errno));
		exit(EXIT_FAILURE);
	}

	pid_t pid = fork();
	if (pid < 0) {
		fprintf(stderr, "Unable to daemonize (fork(), no logs have been opened yet)\n");
		exit(EXIT_FAILURE);
	}

	if (pid > 0) { // parent
		exit(EXIT_SUCCESS);
	}

	// child

	umask(0);

	openlog(config.daemonName().c_str(), LOG_PID, LOG_USER);

	// start a new session
	if (setsid() < 0) { // it failed
		syslog(LOG_ERR, "Unable to start a session for this process, code %d (%s)", errno, strerror(errno));
		exit(EXIT_FAILURE);
	}

	if (chdir("/") < 0) { // cannot chdir to root for some reason
		syslog(LOG_ERR, "Unable to change directory to \"/\", code %d (%s)", errno, strerror(errno));
		exit(EXIT_FAILURE);
	}

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
}

void Server::setupEvents() {
	ebase = event_base_new();
	if (!ebase) {
		syslog(LOG_ERR, "Failed to initialize event_base");
		stop();
		exit(EXIT_FAILURE);
	}

	ehttp = evhttp_new(ebase);
	if (!ehttp) {
		syslog(LOG_ERR, "Failed to initialize evhttp");
		stop();
		exit(EXIT_FAILURE);
	}

	if (evhttp_bind_socket(ehttp,
							Configuration::instance().listenAddress().c_str(),
							Configuration::instance().listenPort())) {
		syslog(LOG_ERR, "Failed to bind socket");
		stop();
		exit(EXIT_FAILURE);
	}

	evhttp_set_gencb(ehttp, request_cb, NULL);
}

void Server::setupSignals() const {
	signal(SIGTERM, signal_handler);
	signal(SIGHUP, signal_handler);
	signal(SIGINT, signal_handler);
	signal(SIGCHLD, signal_handler);
}

void Server::spawnWorkers(int num) {
	pid_t pid = fork();
	if (pid < 0) {
		syslog(LOG_ERR, "Cannot spawn worker");
	} else if (pid > 0) { // parent
		if (num > 1) spawnWorkers(num-1);
	} else { // child
		if (event_reinit(ebase) == -1) { // reinit event_base after fork()
			syslog(LOG_ERR, "Cannot reinit event_base in worker");
			exit(EXIT_FAILURE);
		}
		is_master = false;
		syslog(LOG_NOTICE, "New worker has been spawned");
	}
}
